深拷贝 浅拷贝

Question 1(基础概念)

请你解释一下:什么是浅拷贝(shallow copy)?什么是深拷贝(deep copy)?它们的核心区别是什么?

 

 

浅拷贝(shallow copy)和深拷贝(deep copy)的核心区别在于:是否递归复制对象的引用类型属性

1、浅拷贝

浅拷贝只会复制对象的第一层属性,如果属性值是引用类型(对象/数组),复制的是引用地址,而不是值本身。

因此:

2、深拷贝

深拷贝会递归复制对象的所有层级,生成一个完全独立的新对象

因此修改任意层级的数据都不会影响原对象

3、核心区别总结

本质区别是:


✅ Standard Answer (English)

The key difference between shallow copy and deep copy is whether nested reference types are recursively copied or not.

1、Shallow Copy

A shallow copy only copies the first level properties of an object. If a property is a reference type (like an object or array), it copies the reference address instead of the actual value.

So:

2、Deep Copy

A deep copy recursively copies all levels of an object, creating a fully independent clone.

So:

3、Key Difference

 

 

Question 2(Shallow Copy Methods)

请你说一下:在 JavaScript 中,常见的浅拷贝方式有哪些?(至少说出 3 种) Can you list common ways to perform shallow copy in JavaScript? (at least 3 methods)

 

常见浅拷贝方式(JavaScript)

  1. Object.assign()

中文: Object.assign 会创建一个新对象,并把第一层属性复制进去,因此是浅拷贝。

English: Object.assign creates a new object and copies first-level properties into it, making it a shallow copy.


  1. 展开运算符(spread operator)

中文: 展开运算符会复制对象的第一层属性,但嵌套对象仍然是引用。

English: The spread operator copies only the first-level properties, while nested objects are still referenced.


  1. Array 方法(数组浅拷贝)

中文: 这些方法都会生成一个新的数组,但内部元素如果是引用类型,仍然共享。

English: These methods create a new array, but nested reference types are still shared.

 

 

Question 3(Deep Understanding)

为什么 JSON.parse(JSON.stringify(obj)) 可以实现深拷贝?它有什么缺陷? Why can JSON.parse(JSON.stringify(obj)) achieve deep copy? What are its limitations?

 

问题一:

中文解释

这个方法的本质是:

先把对象转成 JSON 字符串,再重新解析成一个新对象

过程是:

  1. JSON.stringify(obj) 👉 把 JS 对象“序列化”为字符串(会丢失引用关系)
  2. JSON.parse(...) 👉 再把字符串解析成一个全新的对象

✔ 所以它天然“断开引用”,实现类似深拷贝效果

 

English explanation

The idea is:

Convert the object into a JSON string, then parse it back into a new object.

Steps:

  1. JSON.stringify(obj) serializes the object into a string, removing references.
  2. JSON.parse(...) creates a completely new object from that string.

✔ This breaks reference links, which makes it behave like a deep copy.

 

问题二:

中文答案,这个比较多,看一下理解即可:

  1. 不能拷贝函数

👉 会被直接忽略

  1. 不能拷贝 undefined / Symbol

👉 会丢失

  1. 不能处理循环引用(会报错)

👉 会直接报错:

Converting circular structure to JSON

  1. 会丢失特殊对象类型

比如:

 

英文回答:

 

Question 4

你能手写一个“完整支持对象 + 数组 + 循环引用”的深拷贝吗?思路是什么?

Can you implement a deep clone function that supports objects, arrays, and circular references? What is your approach?


先讲思路:

实现深拷贝的核心是三件事:

1️⃣ 判断类型

区分:

2️⃣ 区分对象和数组

3️⃣ 解决循环引用(重点)

用一个结构记录“已经拷贝过的对象”:

👉 MapWeakMap

作用:防止对象无限递归

 

Core idea

To implement deep clone, we need to:

  1. Return primitive values directly
  2. Recursively copy objects and arrays
  3. Track visited objects to handle circular references

Key technique: WeakMap

We use WeakMap to store already cloned objects:


代码:

为什么需要map来解决循环引用?

那如果加上了map,那map做了什么呢?

第一步:克隆对象 a

  1. 检查表:执行 map.has(a)。表是空的,返回 false
  2. 创建空壳const resultA = {}
  3. 做登记:执行 map.set(a, resultA)。此时 map 记录了:“如果你再遇到原对象 a,请直接使用新对象 resultA。”
  4. 递归属性:开始克隆 a.friend(即对象 b)。

第二步:克隆对象 b(在 a 的递归中)

  1. 检查表:执行 map.has(b)。表里只有 a,没有 b,返回 false
  2. 创建空壳const resultB = {}
  3. 做登记:执行 map.set(b, resultB)。此时 map 记录了:{ a -> resultA, b -> resultB }
  4. 递归属性:开始克隆 b.friend(即对象 a)。

第三步:再次遇到对象 a(致命时刻)

  1. 检查表:执行 map.has(a)命中! 返回 true

  2. 直接返回:执行 return map.get(a)

    • 关键点:这里不再进入 deepClone(a) 的递归,而是直接把之前创建的 resultA 给吐出来。
  3. 链路闭合resultB.friend 被赋值为 resultA

第四步:回溯

  1. b 克隆完成,返回 resultB
  2. resultA.friend 被赋值为 resultB
  3. 大功告成:返回 resultA

ES6 新特性

Question 1(基础)

你能说一下 ES6 你最常用的几个新特性有哪些吗?(至少说 3 个) What are some ES6 features you commonly use? (at least 3)

 

中文

我常用的 ES6 特性有:

  1. let / const:用于块级作用域,避免变量提升带来的问题
  2. 箭头函数:简化函数写法,并且没有自己的 this
  3. 解构赋值:可以方便地从对象或数组中提取数据
  4. 模块化(import / export):用于代码拆分和复用

English

Some ES6 features I frequently use include:

  1. let / const for block scoping and avoiding hoisting issues
  2. Arrow functions for concise syntax and lexical this
  3. Destructuring for extracting values from objects and arrays
  4. ES Modules (import / export) for better code organization
  5. Spread operators for copy object or array.

 

Question 2(this + 箭头函数)

箭头函数和普通函数的 this 有什么区别? What is the difference between this in arrow functions and regular functions?

 

中文高分表达

箭头函数和普通函数最大的区别在于 this 的绑定方式不同。 普通函数的 this 是在调用时决定的,取决于调用方式; 而箭头函数没有自己的 this,它的 this 是在定义时就确定的,会继承外层作用域的 this。

English 口语版

The main difference is how this is determined. For regular functions, this is decided at call time depending on how the function is invoked. For arrow functions, this is lexically bound, meaning it is determined at definition time and inherited from the outer scope.


加强理解:

image-20260507163254030

因为箭头函数的this是在定义是确定的,所以第二个输出是Alice。

 

Question 3(进阶)

箭头函数为什么不能作为构造函数? Why can't arrow functions be used as constructors?

 

中文

箭头函数不能作为构造函数,主要有两个原因:

1️⃣ 没有 prototype

👉 构造函数必须有 prototype,用于实例继承

 

2️⃣ 没有自己的 this

箭头函数的 this继承外层作用域的

👉 但构造函数需要一个新的 this(指向实例)

❗ 直接结果

👉 会报错:

Person is not a constructor

 

✅ 一句话总结(面试必杀)

箭头函数不能作为构造函数,因为它没有 prototype,并且没有自己的 this


✅ English Version

Arrow functions cannot be used as constructors for two main reasons:

  1. No prototype

Constructors must have a prototype for instances to inherit from.

 

  1. No own this

Arrow functions do not have their own this; they inherit this from the outer scope.

👉 But constructors require a new this bound to the instance.

Result

Person is not a constructor


"Arrow functions cannot be used as constructors primarily because they lack two things: their own this binding and a prototype property."

"箭头函数不能作为构造函数,主要是因为它缺少两样东西:它没有自己的 this 绑定,也没有 prototype 属性。"

 

"When you use the new keyword, JS tries to create a new object and bind it to the function's this. But arrow functions inherit this from the surrounding scope (lexical this). They don't have a 'blank' this that new can point to."

"当你使用 new 关键字时,JS 会尝试创建一个新对象并把它绑定到函数的 this 上。但箭头函数是从外层作用域继承 this 的(词法 this。它没有一个可以被 new 指向的‘空白’ this。"

 

"Every constructor needs a prototype so that the new instance can inherit methods. Arrow functions don't have a .prototype property at all, so there’s no blueprint for the new object to follow."

"每个构造函数都需要一个 prototype 属性,以便新实例可以继承方法。箭头函数完全没有 .prototype 属性,所以新对象也就没有可以遵循的‘蓝图’。"

 

"If you try to call an arrow function with new, JS will throw a TypeError immediately. It’s not just that it won't work—the engine is literally designed to prevent it."

"如果你尝试对箭头函数使用 new,JS 会立刻抛出 TypeError。这不只是能不能用的问题,而是引擎从设计上就禁止了这种行为。"

 

Question 4(陷阱题)

箭头函数有没有 arguments?如果没有,怎么获取参数? Do arrow functions have arguments? If not, how can you access arguments?

 

✅ 一句话总结(面试用)

箭头函数没有自己的 arguments,它会继承外层作用域的 arguments,通常用 ...args 来获取参数

✅ English Version

👉 No, arrow functions do not have their own arguments object. They inherit arguments from the outer scope.

 

那么如何获取参数呢?使用...args来获取:

How to access arguments?

👉 Use rest parameters:

Arrow functions don’t have their own arguments object.They inherit arguments from the outer scope. use rest parameters instead.


案例说明:箭头函数没有自己的 arguments,它会继承外层作用域的 arguments。如果想拿到传递给箭头函数的参数,需要使用rest parameters,也就是...args来获取。

image-20260507164142263

 

 

Question 5(解构赋值进阶)

解构赋值有哪些常见用法?以及它有哪些坑点? What are common use cases of destructuring, and what are its pitfalls?

 

English

Common use cases:

坑点:

解构赋值我在使用中主要注意几个点: 第一,默认值只在属性是 undefined 的时候才生效,如果是 null 是不会触发默认值的。 第二,如果对 null 或 undefined 进行解构会直接报错,所以一般会加一个兜底,比如用 || {} 第三,嵌套解构的时候,如果中间某一层是 undefined,也会报错,需要给默认值。 还有一个是,数组解构是按顺序来的,对象解构是按 key 来的,这一点要区分。

✅ English(面试版)

When using destructuring, there are a few important pitfalls: First, default values only work when the value is undefined, not null. Second, destructuring null or undefined will throw an error, so we usually provide a fallback like || {}. Third, nested destructuring can fail if an intermediate property is undefined, so we need default values there. Also, array destructuring is position-based, while object destructuring is key-based.


深入理解:

1、常见用法

1️⃣ 对象解构

2️⃣ 数组解构

3️⃣ 函数参数解构

2、坑点

🧠 坑 1:默认值只在 undefined 时生效

👉 不会使用默认值

✅ 结论

默认值只在属性为 undefined 时才生效

English

Default values only apply when the value is undefined, not null.


🧠 坑 2:解构 null / undefined 会直接报错

✅ 解决方案

English

Destructuring null or undefined will throw an error.

Solution:


🧠 坑 3:嵌套解构容易报错

👉 如果 a 是 undefined → 直接崩

✅ 安全写法


🧠 坑 4:变量重命名

👉 不是赋值,而是重命名


🧠 坑 5:数组解构是按位置,不是按名字

👉 顺序非常重要

 

Question 6(正式开始)

扩展运算符(...)和 rest 参数有什么区别? What is the difference between the spread operator and rest parameters?

 

中文

你可以这样说:

扩展运算符和 rest 参数本质上是同一个语法 ...,但使用场景不同。

spread 是用来“展开”的,比如把数组或对象拆开,用在赋值或者函数调用的时候。

rest 是用来“收集”的,比如在函数参数中,把多个参数收集成一个数组。

一个是往外展开,一个是往里收集。

English

Spread and rest use the same syntax ..., but they serve different purposes.

Spread is used to expand values, like spreading an array or object.

Rest is used to collect values, usually in function parameters.

One expands, the other collects.

 

 

Question 7(new 核心)

new 关键字在 JavaScript 中做了什么? What does the new keyword do in JavaScript?

 

中文

new 关键字在 JavaScript 中主要做了四件事。

第一,它会创建一个新的空对象。 第二,它会把这个新对象的原型指向构造函数的 prototype。 第三,它会把构造函数内部的 this 绑定到这个新对象上。 第四,如果构造函数没有显式返回对象,就默认返回这个新对象。

✅ English version

The new keyword in JavaScript performs four steps:

First, it creates a new empty object. Second, it sets the object's prototype to the constructor's prototype. Third, it binds this inside the constructor to the new object. Fourth, it returns the object unless the constructor explicitly returns another object.


方便记忆:

new = 创建对象 + 绑定 this + 继承 prototype + 返回对象

 

Question 8 (手写 new)

Can you implement the new operator manually? 你能手写 new 的实现吗?

 

 

Pure Function

Q1: 什么是纯函数?为什么它在 React 中很重要?

"A Pure Function is a function that returns the same output given the same input and has no side effects." "纯函数是指给定相同的输入,永远返回相同输出,且没有副作用的函数。"

"In React, pure functions make the UI predictable. It allows React to skip re-renders using memo or useMemo because it knows the output only changes when the input changes." "在 React 中,纯函数让 UI 变得可预测。它让 React 能够通过 memouseMemo 跳过不必要的渲染,因为 React 知道只有当输入变化时,输出才会变化。"

 

一个纯函数必须满足两个条件:

  1. 相同输入,永远得到相同输出(不依赖外部随机因素,如 Date.now() 或全局变量)。
  2. 没有副作用(Side Effects):它不修改外部变量,不发网络请求,不打印日志,它只负责计算并返回。

为什么 React 执着于此? 因为 React 需要“可预测性” 如果你的组件函数是纯的,React 就可以放心地缓存它的渲染结果。如果你的函数像写法 A 那样偷偷改了外面的变量,React 就无法追踪到底发生了什么变化。

 

高阶函数(Higher-Order Functions, HOF)

这是 React 逻辑复用的核心。

定义: 一个函数如果能接收另一个函数作为参数,或者返回一个函数,它就是高阶函数。

 

 

Q2: 为什么不使用for循环来操作DOM,而是使用map方法

"The biggest difference is that for loops are Imperative, while map() is Declarative. In React, we prefer telling the UI what to render rather than how to step through the DOM."

"最大的区别在于 for 循环是命令式的,而 map() 是声明式的。在 React 中,我们更倾向于告诉 UI 要渲染什么,而不是一步步告诉它如何去操作 DOM。"

 

A. 不可变性 (Immutability)

"map() creates a new array instead of modifying the original one. This aligns perfectly with React’s 'Immutability' principle, making state tracking much easier."

"map() 会创建一个新数组,而不是修改原数组。这完美契合 React 的‘不可变性’原则,让状态追踪变得简单得多。"

B. 表达式 vs 语句 (Expression vs Statement)

"In JSX, we can only embed expressions. Since map() returns a value, it can be used directly inside our templates. A for loop is a statement, which would require extra code to push items into an array."

"在 JSX 中,我们只能嵌入表达式。因为 map() 有返回值,它可以直接写在模板里。而 for 循环是语句,需要额外的代码(比如先定义一个空数组再 push)才能工作。"

C. 链式调用 (Chainability)

"map() allows for easy chaining. We can filter the data and then map it in one go, which leads to cleaner and more readable code."

"map() 支持链式调用。我们可以先 filter 过滤数据,然后紧接着 map 渲染,这让代码更加简洁易读。"